﻿/*
 * Copyright 2014 Francois Karman
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using Quartz.WebManager.Model;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Linq;

namespace Quartz.WebManager.Logic
{
    /// <summary>
    /// Manages the connection to the database.
    /// </summary>
    public class DataAccess
    {
        /// <summary>
        /// The name of the associated data source.
        /// </summary>
        private const string DataSourceName = "QuartzWebDS";

        /// <summary>
        /// An object used to make <see cref="DataAccess.Instance"/> thread-safe.
        /// </summary>
        private static readonly object lockObject = new object();

        /// <summary>
        /// The unique instance of the class.
        /// </summary>
        private static DataAccess instance = null;

        /// <summary>
        /// The factory used to create connections to the database.
        /// </summary>
        private readonly DbProviderFactory factory;

        /// <summary>
        /// The connection string used to initialize the connections to the database.
        /// </summary>
        private readonly string connectionString;

        /// <summary>
        /// Prevents a default instance of the <see cref="DataAccess"/> class from being created.
        /// </summary>
        private DataAccess()
        {
            ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings
                .Cast<ConnectionStringSettings>()
                .FirstOrDefault(c => string.Equals(c.Name, DataSourceName, StringComparison.OrdinalIgnoreCase));
            if (settings == null)
            {
                string format = "The configuration doesn't contain a valid data source ({0})";
                string message = string.Format(format, DataSourceName);
                throw new Exception(message);
            }

            string providerName = settings.ProviderName;
            this.factory = DbProviderFactories.GetFactory(providerName);
            this.connectionString = settings.ConnectionString;
        }

        /// <summary>
        /// Gets the unique instance of the class.
        /// </summary>
        public static DataAccess Instance
        {
            get
            {
                lock (lockObject)
                {
                    if (instance == null)
                    {
                        instance = new DataAccess();
                    }

                    return instance;
                }
            }
        }

        /// <summary>
        /// Initializes the line in the data table associated with a specific scheduler.
        /// </summary>
        /// <param name="schedulerId">The identifier of the scheduler instance.</param>
        public void SchedulerStateInitialize(string schedulerId)
        {
            using (IDbConnection connection = this.CreateConnection())
            using (IDbCommand command = connection.CreateCommand())
            {
                command.CommandText = "insert into QWM_SCHEDULERS (SCHEDULER, STATE, DATE_CHANGE) values (@scheduler, '', getdate())";
                command.AddParameter("@scheduler", schedulerId);
                command.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Saves the change of mode for a specific scheduler.
        /// </summary>
        /// <param name="schedulerId">The identifier of the scheduler instance.</param>
        /// <param name="state">The new state of the scheduler.</param>
        public void SchedulerChangeState(string schedulerId, string state)
        {
            using (IDbConnection connection = this.CreateConnection())
            using (IDbCommand command = connection.CreateCommand())
            {
                command.CommandText = "update QWM_SCHEDULERS set STATE = @state, DATE_CHANGE = getdate() where SCHEDULER = @scheduler";
                command.AddParameter("@scheduler", schedulerId);
                command.AddParameter("@state", state);
                command.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Saves an error that occurs for a specific scheduler.
        /// </summary>
        /// <param name="schedulerId">The identifier of the scheduler instance.</param>
        /// <param name="message">The error message.</param>
        /// <param name="cause">The exception that causes the error.</param>
        public void SchedulerError(string schedulerId, string message, Exception cause)
        {
            using (IDbConnection connection = this.CreateConnection())
            using (IDbCommand command = connection.CreateCommand())
            {
                command.CommandText = "insert into QWM_ERRORS (DATE_FIRE, SCHEDULER, MESSAGE, DETAIL) values (getdate(), @scheduler @message, @detail)";
                command.AddParameter("@scheduler", schedulerId);
                command.AddParameter("@message", message);
                command.AddParameter("@detail", cause.ToString());
                command.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Retrieves all the current schedulers.
        /// </summary>
        /// <returns>The list of all the schedulers.</returns>
        public IEnumerable<SchedulerInstance> RetrieveSchedulerStates()
        {
            using (IDbConnection connection = this.CreateConnection())
            using (IDbCommand command = connection.CreateCommand())
            {
                command.CommandText = "select SCHEDULER, STATE, DATE_CHANGE from QWM_SCHEDULERS";
                using (IDataReader reader = command.ExecuteReader())
                {
                    ICollection<SchedulerInstance> results = new LinkedList<SchedulerInstance>();
                    while (reader.Read())
                    {
                        results.Add(new SchedulerInstance()
                        {
                            SchedulerInstanceId = reader["SCHEDULER"] as string,
                            State = reader["STATE"] as string,
                            ChangeDate = (DateTime)reader["DATE_CHANGE"],
                        });
                    }

                    return results;
                }
            }
        }

        /// <summary>
        /// Retrieves all the errors that occurs during scheduling tasks.
        /// </summary>
        /// <param name="offset">The offset for the result (number of skipped elements).</param>
        /// <param name="limit">The number of elements to return.</param>
        /// <returns>The list of all the task errors.</returns>
        public IEnumerable<SchedulerError> RetrieveSchedulerErrors(int offset, int limit)
        {
            using (IDbConnection connection = this.CreateConnection())
            using (IDbCommand command = connection.CreateCommand())
            {
                command.CommandText = @"
  select SCHEDULER, DATE_FIRE, MESSAGE, DETAIL
    from QWM_ERRORS
order by DATE_FIRE
  offset @offset rows fetch next @limit rows only";
                command.AddParameter("@offset", offset);
                command.AddParameter("@limit", limit);

                using (IDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        yield return new SchedulerError()
                        {
                            SchedulerInstanceId = reader["SCHEDULER"] as string,
                            FireDate = (DateTime)reader["DATE_FIRE"],
                            Message = reader["MESSAGE"] as string,
                            Detail = reader["DETAIL"] as string,
                        };
                    }
                }
            }
        }

        /// <summary>
        /// Opens a new connection to the extension database.
        /// </summary>
        /// <returns>The new and opened connection.</returns>
        private IDbConnection CreateConnection()
        {
            DbConnection connection = null;
            try
            {
                connection = this.factory.CreateConnection();
                connection.ConnectionString = this.connectionString;
                connection.Open();
                return connection;
            }
            catch (Exception)
            {
                if (connection != null)
                {
                    connection.Dispose();
                }

                throw;
            }
        }
    }
}
